package com.ic.common;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.ic.domain.AppRules;
import com.ic.domain.Rule;
import com.ic.service.RuleService;
import com.ic.util.DrlUtil;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang.time.DateUtils;
import org.drools.core.impl.InternalKnowledgeBase;
import org.drools.core.impl.KnowledgeBaseFactory;
import org.kie.api.KieBase;
import org.kie.api.io.ResourceType;
import org.kie.internal.builder.KnowledgeBuilder;
import org.kie.internal.builder.KnowledgeBuilderErrors;
import org.kie.internal.builder.KnowledgeBuilderFactory;
import org.kie.internal.io.ResourceFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;

@Component
public class DroolsContainer {
    private Map<String, RuleContext> contextMap = Maps.newConcurrentMap();

    private Map<String, AppRules> appIdRuleContextMap = Maps.newConcurrentMap();

    private Map<String, KieBase> kieBaseMap = Maps.newConcurrentMap();

    @Autowired
    private RuleService ruleService;

    public KieBase getKieBase(String appId) {
        return kieBaseMap.get(appId);
    }

    public List<RuleContext> getRuleContext() {
        return Lists.newArrayList(contextMap.values());
    }

    public void checkAndUpdate(String appId) {
        AppRules appRules = appIdRuleContextMap.get(appId);
        AppRules drs = ruleService.getAppRules(appId);
        if (null == drs) return;

        if (null != appRules && appRules.getVersion().equals(drs.getVersion())) {
            return;
        }

        updateKieBase(appId, drs.getRules());

        updateRuleContexts(appId, drs);
    }

    public void delete(String ruleId) {
        contextMap.remove(ruleId);
        kieBaseMap.remove(ruleId);
    }

    private void updateRuleContexts(String appId, AppRules appRules) {
        appIdRuleContextMap.put(appId, appRules);
        appRules.getRules().forEach(r -> {
            RuleContext ruleContext = Optional.ofNullable(contextMap.get(r.getId()))
                    .map(c -> {
                        c.setMatchNum(0);
                        c.setMatchStart(null);
                        c.setTriggerStart(null);
                        c.setTriggerNum(0);
                        c.setDuring(r.getDuring());
                        c.setMatchDuring(r.getMatchDuring());
                        c.setExpression(r.getExpression());
                        c.setId(r.getId());
                        c.setVersion(r.getVersion());
                        c.setName(r.getName());
                        return c;
                    }).orElse(new RuleContext(r));
            contextMap.put(r.getId(), ruleContext);
        });
    }

    public void updateKieBase(String appId, List<Rule> rules) {
        String drl = DrlUtil.drl(rules);
        updateKieBase(appId, drl);
    }

    public void updateKieBase(String appId, String drl) {
        KnowledgeBuilder builder = KnowledgeBuilderFactory.newKnowledgeBuilder();
        builder.add(ResourceFactory.newByteArrayResource(drl.getBytes()), ResourceType.DRL);

        KnowledgeBuilderErrors errors = builder.getErrors();
        if (CollectionUtils.isNotEmpty(errors)) {
            throw new IllegalArgumentException(errors.iterator().next().getMessage());
        }

        InternalKnowledgeBase base = KnowledgeBaseFactory.newKnowledgeBase();

        base.addPackages(builder.getKnowledgePackages());

        kieBaseMap.put(appId, base);
    }

    public void triggered(RuleContext context) {
        Integer during = context.getDuring();
        Date start = context.getTriggerStart();
        Date now = new Date();
        Integer triggerNum = Optional.ofNullable(context.getTriggerNum()).orElse(0);
        if (null == during || during < 1) {
            triggerNum++;
        } else if (null != start && DateUtils.addSeconds(start, during).after(now)) {
            triggerNum++;
        } else {
            triggerNum = 1;
            context.setTriggerStart(now);
        }
        context.setTriggerNum(triggerNum);
        contextMap.put(context.getId(), context);
    }

    public void matched(RuleContext context) {
        Integer during = context.getMatchDuring();
        Date start = context.getMatchStart();
        Date now = new Date();
        Integer matchNum = Optional.ofNullable(context.getMatchNum()).orElse(0);
        if (null == during || during < 1) {
            matchNum++;
        } else if (null != start && DateUtils.addSeconds(start, during).after(now)) {
            matchNum++;
        } else {
            matchNum = 1;
            context.setMatchStart(now);
        }
        context.setMatchNum(matchNum);
        contextMap.put(context.getId(), context);
    }
}